import java.util.ArrayList;
import java.util.HashMap;
import java.awt.Color;
import java.awt.Label;
import java.awt.TextField;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;

import java.awt.Choice;
import processing.core.*;


public class Main extends PApplet {
	private TransitGraph transitMap; // transit map containing stations and edges
	private HashMap<Integer, StationButton> buttons; // hashmap of stationbuttons using stations' parent id
	private StationButton source; // source for the route
	private StationButton destination; // destination for the route
	private ArrayList<StationPair> route; // list of stations forming the route
	
	MouseWheelEventDemo wheel; // Used to detect scroll up and down
	boolean pressed = false; // If mouse is pressed
	PFont font; // The font for each station name
	
	OSMMercator mercator; // Converts lon and lat to screen coords
	double scale = 1.2; // Scaling used by mercator
	double start_x, start_y; // Starting mouse position for screen drag
	double center_lon; // lon center used by mercator
	double center_lat; // lat center used by mercator
	Double maxLon; // maximum station lon
	Double minLon; // minimum station lon
	Double maxLat; // maximum station lat
	Double minLat; // minimum station lat
	boolean button_pos_change = false;
	int time; //new time
	
	/* Default constructor for Main */
	public Main() {
		transitMap = new TransitGraph(); // initialize the transit map
	}
	
	/* getter for the transit map */
	public TransitGraph getTransitMap() {
		return transitMap;
	}
	
	/* Sets up the PApplet window for drawing */
	public void setup() {
		size(700, 1000);
		background(255);
		smooth();
		
		font = loadFont("MyriadPro-Bold-14.vlw");
		textFont(font, 12);
		 wheel = new MouseWheelEventDemo();
		 buttons = new HashMap<Integer, StationButton>();
		 source = null;
		 destination = null;
		 route = null;
		
		 /* Find the minimum and maximum for lon and lat. Also assigns a StationButton to
		  * each unique parent id.*/
		 ArrayList<Station> stations = transitMap.getStationsList(); // A list of all the stations
		 maxLon=-Double.MAX_VALUE;
		minLon=Double.MAX_VALUE;
		maxLat=-Double.MAX_VALUE;
		minLat=Double.MAX_VALUE;	
		for (Station st : stations) {
			 maxLon = Math.max(st.getLon(),maxLon);
			 minLon = Math.min(st.getLon(),minLon);
			 maxLat = Math.max(st.getLat(),maxLat);
			 minLat = Math.min(st.getLat(),minLat);
			 
			 if(st instanceof ParentStation) {
				 StationButton bt = new StationButton(st, 10, st.getLon(), st.getLat()); // create a new button from station lon, lat with radius 10
				 bt.setId(st.getParentid()); // set the buttons id
				 buttons.put(new Integer(st.getParentid()), bt);
			 }	 
		}
		
		center_lon = (minLon+maxLon)/2.0; // set center lon
		center_lat = (minLat+maxLat)/2.0; // set center lat
		updateButtonPositions();
		
		TextActionListener tal = new TextActionListener();
			
	}
	
	public void draw() {
		background(255);
		ellipseMode(CENTER);
		fill(255);
		stroke(0);
		strokeWeight(1);

		if(pressed) {
			double dist_x = start_x - mouseX;
			if(dist_x >= 0) {
				Math.max(50.0, dist_x);
			} else {
				Math.min(-50.0, dist_x);
			}
			
			double dist_y = start_y - mouseY;
			if(dist_y >= 0) {
				Math.max(50.0, dist_y);
			} else {
				Math.min(-50.0, dist_y);
			}
			
			center_lon += dist_x*(scale*(maxLon-minLon)/width)*.1;
			center_lat -= dist_y*(scale*(maxLon-minLon)/width)*.1;
			
			if(center_lon < minLon) {
				center_lon = minLon;
			}
			if(center_lon > maxLon) {
				center_lon = maxLon;
			}
			if(center_lat < minLat) {
				center_lat = minLat;
			}
			if(center_lat > maxLat) {
				center_lat = maxLat;
			}
			
			if(dist_x != 0.0 || dist_y != 0.0) {
				button_pos_change = true;
			}
		}
		
		if(button_pos_change) {
			updateButtonPositions();
		}
		
		ArrayList<Edge> edges_list = transitMap.getEdges();
		for (Edge edge : edges_list) {
			Station src = edge.getSource();
			Station dest = edge.getDestination();
			
			if(src.sibling(dest)) {
				continue;
			}
			
			StationButton bt1 = buttons.get(src.getParentid());
			StationButton bt2 = buttons.get(dest.getParentid());
			
			Color color = lineColor(src.getLine());
			stroke(color.getRed(), color.getGreen(), color.getBlue(), 50);

			strokeWeight(8);
			line((float)bt1.getX(), (float)bt1.getY(), (float)bt2.getX(), (float)bt2.getY());
			
		}
		
		if(route != null) {
			for(int i = 0; i < route.size()-1; i++) {
				Station st1 = route.get(i).getStation();
				StationButton bt1 = buttons.get(st1.getParentid());
				
				Station st2 = route.get(i+1).getStation();
				StationButton bt2 = buttons.get(st2.getParentid());
				String key = new String(st1.hashCode() + "" + st2.hashCode());
				
				Color color = lineColor(st1.getLine());
				
				if(transitMap.containsEdge(key.hashCode())) {
					stroke(color.getRed(), color.getGreen(), color.getBlue());
					strokeWeight(8);
					line((float)bt1.getX(), (float)bt1.getY(), (float)bt2.getX(), (float)bt2.getY());
				}
			}
		}
		
		
		ArrayList<StationButton> buttons_list = new ArrayList(buttons.values());	
		for (StationButton bt : buttons_list) {
			drawButton(bt);
		}
		
		int box_x = 20;
		int box_y = height/2;
		int box_h = 35;
		int box_w = 115;
		int box_space = 10;
		if(route != null) {
			for(int i = 0; i < route.size()-1; i++) {
				Station st1 = route.get(i).getStation();			
				Station st2 = route.get(i+1).getStation();			
				Color color = lineColor(st1.getLine());
				
				rectMode(CORNER);
				noStroke();
				if(i == 1) {
					fill(color.getRed(), color.getGreen(), color.getBlue());
					rect(box_x, box_y, box_w, box_h);
					fill(255);
					String st_txt = "" + st1.getName() + " " + st1.getDirection();
					String time_txt = "Depart at " + minsToTime(time + route.get(i).getWeight());
					text(st_txt, box_x + 5, box_y + 15);
					text(time_txt, box_x + 5, box_y + 28);
					box_y += box_h + box_space;
				} else if(i != 0 && st1.getLine().equals(route.get(i-1).getStation().getLine()) && !st1.getLine().equals(st2.getLine()) ) {
					color = lineColor(st1.getLine());
					fill(color.getRed(), color.getGreen(), color.getBlue());
					rect(box_x, box_y, box_w, box_h);
					fill(255);
					String st_txt = "" + st1.getName() + " " + st1.getDirection();
					String time_txt = "Arrive at " + minsToTime(time + route.get(i).getWeight());
					text(st_txt, box_x + 5, box_y + 15);
					text(time_txt, box_x + 5, box_y + 28);
					box_y += box_h + box_space;
				} else if(i != 0 && !st1.getLine().equals(route.get(i-1).getStation().getLine()) && st1.getLine().equals(st2.getLine()) ) {
					color = lineColor(st1.getLine());
					fill(color.getRed(), color.getGreen(), color.getBlue());
					rect(box_x, box_y, box_w, box_h);
					fill(255);
					String st_txt = "" + st1.getName() + " " + st1.getDirection();
					String time_txt = "Depart at " + minsToTime(time + route.get(i).getWeight());
					text(st_txt, box_x + 5, box_y + 15);
					text(time_txt, box_x + 5, box_y + 28);
					box_y += box_h + box_space;
				}
			}
		}
		
		fill(color(0,0,0), 35);
		noStroke();
		ellipse(width/2, height/2, 5, 5);
		
		if(pressed) {
			stroke(color(0,0,0), 35);
			strokeWeight(3);
			double x = (start_x - mouseX);
			double y = (start_y - mouseY);
			double len = Math.sqrt(x*x + y*y);
			double n_x = x / len;
			double n_y = y / len;
			n_x *= 25.0;
			n_y *= 25.0;
			line(width/2, height/2, width/2 + (int)n_x, height/2 + (int)n_y);
		}

	}
	
	public void drawButton(StationButton bt) {
		float x = (float)bt.getX();
		float y = (float)bt.getY();
		
		float disX = x - mouseX;
		float disY = y - mouseY;
		  
		  noStroke();
		  if((sqrt(sq(disX) + sq(disY)) < bt.getRadius()/2 && !pressed) || bt.getSelected()) {
			  fill(0);
			  text(bt.getStation().getName(), x + 10 , y);
			  
				fill(0);
				ellipse(x, y, bt.getRadius() + 2, bt.getRadius() + 2);
				fill(255);
				ellipse(x, y, bt.getRadius() - bt.getRadius()/4, bt.getRadius() - bt.getRadius()/4);
		  } else {
				fill(0);
				ellipse(x, y, bt.getRadius(), bt.getRadius());
				fill(255);
				ellipse(x, y, bt.getRadius() - bt.getRadius()/4, bt.getRadius() - bt.getRadius()/4);
		  }
	}
	
	public void mousePressed() {
		ArrayList<StationButton> buttons_list = new ArrayList(buttons.values());	
		for (StationButton bt : buttons_list) {
		
		float x = (float)bt.getX();
		float y = (float)bt.getY();
		
		float disX = x - mouseX;
		float disY = y - mouseY;
		if(sqrt(sq(disX) + sq(disY)) < bt.getRadius()/2 && !pressed) {
			  if(source == null) {
				  source = bt;
				  bt.setSelected(true);
			  } else if(source != null && destination == null && !source.equals(bt)) {
				  //  If the same button is selected as the source and destination
				  destination = bt;
				  bt.setSelected(true);
			  } else if(source != null && destination != null ) {
				  source.setSelected(false); // deselect old button
				  source = bt;
				  bt.setSelected(true);
				  destination.setSelected(false); // deselect destination
				  destination = null;
				  route = null;
			  }
			  
			  if(source != null && destination != null) {
				  generateRoute();
			  }
			  
			  return;
		 }
		}
		  
		if(!pressed) {
			pressed = true;
			start_x = mouseX;
			start_y = mouseY;
		}
	}
	
	public void generateRoute() {
		Station src = source.getStation();
		Station dest = destination.getStation();
		
		ArrayList<Station> stations = transitMap.getStationsList();
		ArrayList<Edge> parent_edges = new ArrayList<Edge>();
		for(Station child : stations) {			
			if(!src.equals(child) && src instanceof ParentStation && !(child instanceof ParentStation) && src.getChildid() == child.getParentid()) {
				Edge edge = new Edge(src, child, false, -1);
				transitMap.addEdge(edge);
				parent_edges.add(edge);
			}
			if(!dest.equals(child) && dest instanceof ParentStation && !(child instanceof ParentStation) && dest.getChildid() == child.getParentid()) {
				Edge edge = new Edge(child, dest, false, -1);
				transitMap.addEdge(edge);
				parent_edges.add(edge);
			}
		}
		
		
		DijkstraAlgorithm dalgo = new DijkstraAlgorithm(transitMap);
		dalgo.setStarttime(time);
		dalgo.execute(src);

		for(Edge edge : parent_edges) {
			transitMap.removeEdge(edge);
		}
		
		ArrayList<StationPair> route = dalgo.getPath(dest);	
		if(route == null) {
			System.out.println("ERROR: no route found.");
			this.route = null;
			return;
		}
		
		System.out.println("\nRoute from: " + src.getName() + " to " + dest.getName());
		
		// Go through route if have the same sibling then 0 time
		// Eliminate transfers between siblings at the beginning and end
		
		for(StationPair st : route) {
			System.out.println(st.getWeight() + " " + st.getStation());
		}
	
		this.route = route;
	}
	
	public void updateButtonPositions() {
		mercator = new OSMMercator(center_lat, center_lon, scale*(maxLon-minLon)/width, width, height);
		
		ArrayList<StationButton> buttons_list = new ArrayList(buttons.values());	
		for (StationButton bt : buttons_list) {
			bt.setX(mercator.x(bt.getLon()));
			bt.setY(mercator.y(bt.getLat()));
		}
		
		button_pos_change = false;
	}
	
	public void mouseReleased() {
		if(pressed) {
			pressed = false;
		}
	}
	
	public class MouseWheelEventDemo implements MouseWheelListener {
		 public MouseWheelEventDemo() {
		   addMouseWheelListener(this);
		 }
		
		public void mouseWheelMoved(MouseWheelEvent arg0) {
			 String message;
			   int notches = arg0.getWheelRotation();
			   if (notches < 0) {
					   scale -= .01;
					   scale = Math.max(scale, 0.06);
					   button_pos_change = true;
			     message = "Mouse wheel moved UP "
			       + -notches + " notch";
			   } 
			   else {
					   scale += .01;
					   scale = Math.max(scale, 0.06);
					   button_pos_change = true;
			     message = "Mouse wheel moved DOWN "
			       + notches + " notch";
			   }
			   //println(scale);
			
			}
		}
	
	public class TextActionListener implements ItemListener {

		private TextField in;
		private Label label;
		//private LayoutManager layout;
		private Choice box;

		public TextActionListener() {
			
			//layout = new FlowLayout();
			
			in = new TextField(24);
			//in.setBackground(new Color(255, 255, 255));
			
			box = new Choice();
			box.setVisible(true);
			box.add("AM");
			box.add("PM");
			//box.setBackground(new Color(255, 255, 255));
			//TextActionListener tal = new TextActionListener(textField, choice);
			//setLayout(layout);
			//dropdown.addActionListener(tal);
			label = new Label("Arrival time");
			//label.setBackground(new Color(0,0,0,0));
			add(label);
			add(in);
			//in.selectAll();
			add(box);
			box.addItemListener(this);
			
			//this.in = i;
			//in.setText("Please input time. HOURS:MIN");
			//t/his.box = b;
			
		}

		@Override
		public void itemStateChanged(ItemEvent arg0) {
			// TODO Auto-generated method stub
			String s = in.getText();
			int ampm = box.getSelectedIndex();
			int hours;
			int am;
			if(s.equals("")) {

				in.setText("Please input a time.");
				return;

			}
			else{

				String[] tokens = s.trim().split(":");
				// if no colon
				if( s.equals(tokens[0]) ) {

					in.setText("Please input a time.");
					return;

				}
				// check number format
				try {

					hours = new Integer(tokens[0]);

					if( hours > 12 || hours < 0 ) {

						in.setText("Please input a time.");
						return;


					}

					time = new Integer(tokens[1]);

				}catch (NumberFormatException nfe) {

					in.setText("Please input a time.");
					return;

				}

				if( hours == 12 ) {

					if(ampm == 0)
						hours = 0;

				}
				else {

					if(ampm == 1)
						hours += 12;

				}

			}

			time += hours*60;

			System.out.println(s + " = " + time);
		}

	}
	
	public String minsToTime(int mins) {
		int m = (mins%60);	
		int hr = mins/60%24;
	
		String am_pm = "PM";
		if(hr < 12) {
			am_pm = "AM";
		}
		
		// Convert midgnight to 12
		if(hr == 0) {
			hr = 12;
		}
		
		if(hr > 12 && hr < 24) {
			hr -= 12;
		}
		
		String filler = "";
		if(m < 10) {
			filler = "0";
		}
		
		return new String(hr + ":" + filler + m + " " + am_pm);
	}
	
	public Color lineColor(String line) {
		if(line.equals("Blue")) {
			return new Color(0, 161, 222);
		} else if(line.equals("Red")) {
			return new Color(198, 12, 48);
		}else if(line.equals("P")) {
			return new Color(82, 35, 152);
		}else if(line.equals("Y")) {
			return new Color(249, 227, 0);
		}
		else if(line.equals("Pink")) {
			return new Color(226, 126, 166);
		}
		else if(line.equals("G")) {
			return new Color(0, 155, 58);
		}
		else if(line.equals("Org")) {
			return new Color(249, 70, 28);
		}
		else if(line.equals("Brn")) {
			return new Color(98, 54, 27);
		} else {
			return new Color(0, 0, 0);
		}
	}
	
	public static void main(String [] args) {
		PApplet.main(new String[] { "--present", "Main" });
	}
	
}
